package com.shangyueshang.aspect;

import cn.hutool.core.util.IdUtil;
import com.shangyueshang.common.LogConst;
import com.shangyueshang.response.HttpResult;
import com.shangyueshang.utils.IPUtil;
import com.shangyueshang.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author: JokeyZheng
 * @email: zhengjingfeng@ruqimobility.com
 * @created: 2021/10/27 14:56
 * @version: v1.0.0
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 定义切入点，切入点为controller下的所有函数
     * 1) execution(): 表达式主体
     * 2) 第一个public *号：表示返回类型， *号表示所有的类型。
     * 3) 包名：表示需要拦截的包名，后面的两个句点表示当前包和当前包的所有子包。
     * 4) 第二个*号：表示类名，*号表示所有的类。
     * 5) *(..):最后这个星号表示方法名，*号表示所有的方法，后面括弧里面表示方法的参数，两个句点表示任何参数
     */
    @Pointcut("execution(public * com.shangyueshang.*.controller..*.*(..))")
    public void pointCutMethod() {
    }

    @Before("pointCutMethod()")
    public void doBefore(JoinPoint joinPoint) {
        try {
            MDC.put(LogConst.LOG_ID, IdUtil.objectId());
            MDC.put(LogConst.USERNAME, SecurityUtil.getUsername());
            MDC.put(LogConst.REQUEST_START_TIME, String.valueOf(System.currentTimeMillis()));
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
        }
    }

    @Around("pointCutMethod()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        return this.doTask(joinPoint);
    }

    @AfterReturning(returning = "ret", pointcut = "pointCutMethod()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) {
        this.logging(joinPoint, ret);
    }

    @AfterThrowing(throwing = "ret", pointcut = "pointCutMethod()")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable ret) {
        this.logging(joinPoint, ret);
    }

    @After(value = "pointCutMethod()")
    public void finish(JoinPoint joinPoint) {
        // 清除日志记录的信息
        MDC.clear();
    }

    private Object doTask(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }

    private void logging(JoinPoint joinPoint, Object ret) {
        String message = "";
        String messageType = "response";
        if (null != ret) {
            if (ret instanceof Throwable) {
                Throwable ex = (Throwable) ret;
                message = ex.getMessage();
                messageType = "exception";
                log.error("Exception: ", ex);
            } else if (log.isDebugEnabled()) {
                // 日志在DEBUG级别，打印response全量信息
                message = ret.toString();
            } else if (log.isInfoEnabled() && ret instanceof HttpResult) {
                message = (ret).toString();
            } else {
                message = ret.toString();
            }
        }
        // 链路追踪
        long requestStartTime = Long.parseLong(MDC.get(LogConst.REQUEST_START_TIME));
        long requestFinishTime = System.currentTimeMillis();
        long cost = requestFinishTime - requestStartTime;
        String parameters = this.getArgs(joinPoint).toString();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(attributes)) {
            String methodName = joinPoint.getSignature().getName();
            log.info("cost[{}], method[{}], args[{}], {}[{}]", cost, methodName, parameters, messageType, message);
            return;
        }
        HttpServletRequest currentHttpRequest = attributes.getRequest();
        String url = currentHttpRequest.getRequestURL().toString();
        String httpMethod = currentHttpRequest.getMethod();
        String remoteHost = IPUtil.getIpAddress(currentHttpRequest);

        String logFormat = "cost[{}],url[{}],httpMethod[{}],remoteHost[{}],request[{}],{}[{}]";
        if (log.isDebugEnabled()) {
            log.debug(logFormat, cost, url, httpMethod, remoteHost, parameters, messageType, message);
        } else if (log.isInfoEnabled()) {
            log.info(logFormat, cost, url, httpMethod, remoteHost, parameters, messageType, message);
        }
    }

    private List<Object> getArgs(JoinPoint joinPoint) {
        Object[] pointArgs = joinPoint.getArgs();
        List<Object> argList = new ArrayList<>();
        // 过滤参数校验信息
        for (Object object : pointArgs) {
            if (!(object instanceof BindingResult)) {
                argList.add(object);
            }
        }
        return argList;
    }
}
